/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.math4.neuralnet;

import java.util.Collection;
import java.util.Arrays;
import java.util.NoSuchElementException;

import org.junit.Assert;
import org.junit.Test;

import org.apache.commons.rng.UniformRandomProvider;
import org.apache.commons.rng.simple.RandomSource;

import org.apache.commons.math4.neuralnet.twod.NeuronSquareMesh2D;

/**
 * Tests for {@link Network}.
 */
public class NetworkTest {
    private final UniformRandomProvider rng = RandomSource.SPLIT_MIX_64.create();
    private final FeatureInitializer init = FeatureInitializerFactory.uniform(rng, 0, 2);

    @Test
    public void testGetFeaturesSize() {
        final FeatureInitializer[] initArray = {init, init, init};

        final Network net = new NeuronSquareMesh2D(2, false,
                                                   2, false,
                                                   SquareNeighbourhood.VON_NEUMANN,
                                                   initArray).getNetwork();
        Assert.assertEquals(3, net.getFeaturesSize());
    }

    /*
     * Test assumes that the network is
     *
     *  0-----1
     *  |     |
     *  |     |
     *  2-----3
     */
    @Test
    public void testDeleteLink() {
        final FeatureInitializer[] initArray = {init};
        final Network net = new NeuronSquareMesh2D(2, false,
                                                   2, false,
                                                   SquareNeighbourhood.VON_NEUMANN,
                                                   initArray).getNetwork();
        Collection<Neuron> neighbours;

        // Delete 0 --> 1.
        net.deleteLink(net.getNeuron(0),
                       net.getNeuron(1));

        // Link from 0 to 1 was deleted.
        neighbours = net.getNeighbours(net.getNeuron(0));
        Assert.assertFalse(neighbours.contains(net.getNeuron(1)));
        // Link from 1 to 0 still exists.
        neighbours = net.getNeighbours(net.getNeuron(1));
        Assert.assertTrue(neighbours.contains(net.getNeuron(0)));
    }

    /*
     * Test assumes that the network is
     *
     *  0-----1
     *  |     |
     *  |     |
     *  2-----3
     */
    @Test
    public void testDeleteNeuron() {
        final FeatureInitializer[] initArray = {init};
        final Network net = new NeuronSquareMesh2D(2, false,
                                                   2, false,
                                                   SquareNeighbourhood.VON_NEUMANN,
                                                   initArray).getNetwork();

        Assert.assertEquals(2, net.getNeighbours(net.getNeuron(0)).size());
        Assert.assertEquals(2, net.getNeighbours(net.getNeuron(3)).size());

        // Delete neuron 1.
        net.deleteNeuron(net.getNeuron(1));

        try {
            net.getNeuron(1);
        } catch (NoSuchElementException expected) {
          // Ignore
        }

        Assert.assertEquals(1, net.getNeighbours(net.getNeuron(0)).size());
        Assert.assertEquals(1, net.getNeighbours(net.getNeuron(3)).size());
    }

    @Test
    public void testIdentifierAssignment() {
        final FeatureInitializer[] initArray = {init};
        final long[] ids = getIdentifiers(new NeuronSquareMesh2D(4, false,
                                                                3, true,
                                                                SquareNeighbourhood.VON_NEUMANN,
                                                                initArray).getNetwork());

        Assert.assertEquals(12, ids.length);
        Assert.assertEquals(0, ids[0]);
        Assert.assertEquals(11, ids[ids.length - 1]);
    }

    /*
     * Test assumes that the network is
     *
     *  0-----1
     *  |     |
     *  |     |
     *  2-----3
     */
    @Test
    public void testCopy() {
        final FeatureInitializer[] initArray = {init};
        final Network net = new NeuronSquareMesh2D(2, false,
                                                   2, false,
                                                   SquareNeighbourhood.VON_NEUMANN,
                                                   initArray).getNetwork();

        final Network copy = net.copy();

        final Neuron netNeuron0 = net.getNeuron(0);
        final Neuron copyNeuron0 = copy.getNeuron(0);
        final Neuron netNeuron1 = net.getNeuron(1);
        final Neuron copyNeuron1 = copy.getNeuron(1);
        Collection<Neuron> netNeighbours;
        Collection<Neuron> copyNeighbours;

        // Check that both networks have the same connections.
        netNeighbours = net.getNeighbours(netNeuron0);
        copyNeighbours = copy.getNeighbours(copyNeuron0);
        Assert.assertTrue(netNeighbours.contains(netNeuron1));
        Assert.assertTrue(copyNeighbours.contains(copyNeuron1));

        // Delete neuron 1 from original.
        net.deleteNeuron(netNeuron1);

        // Check that the networks now differ.
        netNeighbours = net.getNeighbours(netNeuron0);
        copyNeighbours = copy.getNeighbours(copyNeuron0);
        Assert.assertFalse(netNeighbours.contains(netNeuron1));
        Assert.assertTrue(copyNeighbours.contains(copyNeuron1));
    }

    /**
     * @param net Network.
     * @return the sorted list identifiers.
     */
    private long[] getIdentifiers(Network net) {
        final Collection<Neuron> neurons = net.getNeurons();
        final long[] identifiers = new long[neurons.size()];

        int idx = 0;
        for (Neuron n : neurons) {
            identifiers[idx++] = n.getIdentifier();
        }
        Arrays.sort(identifiers);
        return identifiers;
    }
}
